// ColorConverter.cpp : implementation file
//

#include "stdafx.h"
#include "ColorConverter.h"

CColorConverter::CColorConverter(CxImage* pImage)
{
	this->m_pImage = pImage;
}

// CColorConverter member functions

void CColorConverter::SetDefaultBitfields()
{
	if (m_pImage == NULL)
		return;

	int bpp = m_pImage->GetBPP();
	if (bpp == 16)
	{
		m_dwBitfields[0] = 0x00007C00;
		m_dwBitfields[1] = 0x000003E0;
		m_dwBitfields[2] = 0x0000001F;

		m_wRShift[0] = 10;
		m_wRShift[1] =  5;
		m_wRShift[2] =  0;

		m_wLShift[0] = m_wLShift[1] = m_wLShift[2] = 3;
	}
	else if(bpp == 24 || bpp == 32)
	{
		m_dwBitfields[0] = 0x00FF0000;
		m_dwBitfields[1] = 0x0000FF00;
		m_dwBitfields[2] = 0x000000FF;

		m_wRShift[0] = 16;
		m_wRShift[1] =  8;
		m_wRShift[2] =  0;

		m_wLShift[0] = m_wLShift[1] = m_wLShift[2] = 0;
	}
}

UINT CColorConverter::GetNearestPaletteIndex(RGBQUAD color)
{
	UINT uNearestIndex = CLR_INVALID;
	RGBQUAD* colorTable = m_pImage->GetColorTable();
	if (colorTable)
	{
		DWORD dwNumColors = m_pImage->GetMaxColorTableEntries();

		DWORD dwDiff, dwMinDiff = 0xFFFFFFFF; // any # > 3 * 255
		for (UINT i = 0; i < dwNumColors && dwMinDiff; i++)
		{
			dwDiff =
				abs(colorTable[i].rgbRed - color.rgbRed) +
				abs(colorTable[i].rgbGreen - color.rgbGreen) +
				abs(colorTable[i].rgbBlue - color.rgbBlue);
			if (dwDiff < dwMinDiff)
			{
				dwMinDiff = dwDiff;
				uNearestIndex = i;
			}
		}
	}
	return uNearestIndex;
}

BOOL CColorConverter::GetPaletteEntries(UINT uStartIndex, UINT cEntries, LPRGBQUAD pColors) const
{
	if (!m_pImage)
	{
		::SetLastError(ERROR_INVALID_HANDLE);
		return FALSE;
	}
	if (!cEntries || !pColors)
	{
		::SetLastError(ERROR_INVALID_PARAMETER);
		return FALSE;
	}

	RGBQUAD* colorTable = m_pImage->GetColorTable();
	if (colorTable && uStartIndex + cEntries <=
		m_pImage->GetMaxColorTableEntries())	
	{
		::CopyMemory(pColors, colorTable + uStartIndex,
			cEntries * sizeof(RGBQUAD));
		return TRUE;
	}

	return FALSE;
}

void CColorConverter::SetImage(CxImage* pImage)
{
	this->m_pImage = pImage;
	this->SetDefaultBitfields();
	m_pImage->InitColorTable();
}

CxImage* CColorConverter::GetImage()
{
	return this->m_pImage;
}

DWORD CColorConverter::RGBtoDWORD(RGBQUAD rgb)
{
	DWORD dwValue;
	
	int bpp = m_pImage->GetBPP();
	if (bpp <= 8)
	{
		dwValue = GetNearestPaletteIndex(rgb);
		if (dwValue == CLR_INVALID)
			return CLR_INVALID;
	}
	else if (bpp == 24)
	{
		*(RGBTRIPLE*)&dwValue = *(RGBTRIPLE*)&rgb;
		dwValue &= 0x00FFFFFF;
	}
	else if (bpp == 32)
	{
		*(LPRGBQUAD)&dwValue = rgb;
	}
	else
	{
		dwValue  = (((DWORD)rgb.rgbRed   >> m_wLShift[0]) << m_wRShift[0]);
		dwValue |= (((DWORD)rgb.rgbGreen >> m_wLShift[1]) << m_wRShift[1]);
		dwValue |= (((DWORD)rgb.rgbBlue  >> m_wLShift[2]) << m_wRShift[2]);
	}
	return dwValue;
}

RGBQUAD CColorConverter::DWORDtoRGB(DWORD value)
{
	RGBQUAD rgbResult;
	int bpp = m_pImage->GetBPP();

	if (bpp <= 8)		// <= 8 bits per pixel
	{
		GetPaletteEntries((UINT)value, 1, &rgbResult);
	}
	else if (bpp == 24)
	{
		RGBTRIPLE rgbt = *(RGBTRIPLE*)&value;
		rgbResult.rgbRed = rgbt.rgbtRed;
		rgbResult.rgbGreen = rgbt.rgbtGreen;
		rgbResult.rgbBlue = rgbt.rgbtBlue;
	}
	else if (bpp == 32)
	{
		rgbResult = *(LPRGBQUAD)&value;
	}
	else
	{
		rgbResult.rgbRed = (BYTE)(((m_dwBitfields[0] & value) 
			>> m_wRShift[0]) << m_wLShift[0]);

		rgbResult.rgbGreen = (BYTE)(((m_dwBitfields[1] & value)
			>> m_wRShift[1]) << m_wLShift[1]);

		rgbResult.rgbBlue = (BYTE)(((m_dwBitfields[2] & value)
			>> m_wRShift[2]) << m_wLShift[2]);
	}
	return rgbResult;
}

HSL CColorConverter::RGBtoHSL(RGBQUAD rgb)
{
	HSL hsl;

	double r = (rgb.rgbRed / 255.0);
	double g = (rgb.rgbGreen / 255.0);
	double b = (rgb.rgbBlue / 255.0);

	double minValue = min(min(r, g), b);
	double maxValue = min(max(r, g), b);
	double delta = maxValue - minValue;

	// get luminance value
	hsl.Luminance = (maxValue + minValue) / 2;

	if (delta == 0)
	{
		// gray color
		hsl.Hue = 0;
		hsl.Saturation = 0.0;
	}
	else
	{
		// get saturation value
		hsl.Saturation = (hsl.Luminance < 0.5) ? 
			(delta / (maxValue + minValue)) :
			(delta / (2 - maxValue - minValue));

		// get hue value
		double del_r = (((maxValue - r) / 6) + (delta / 2)) / delta;
		double del_g = (((maxValue - g) / 6) + (delta / 2)) / delta;
		double del_b = (((maxValue - b) / 6) + (delta / 2)) / delta;
		double hue;

		if (r == maxValue)
			hue = del_b - del_g;
		else if (g == maxValue)
			hue = (1.0 / 3) + del_r - del_b;
		else
			hue = (2.0 / 3) + del_g - del_r;

		// correct hue if needed
		if (hue < 0)
			hue += 1;
		if (hue > 1)
			hue -= 1;

		hsl.Hue = (int)(hue * 360);
	}
	return hsl;
}

RGBQUAD CColorConverter::HSLtoRGB(HSL hsl)
{
	RGBQUAD rgb;
	if (hsl.Saturation == 0)
	{
		// gray values
		rgb.rgbRed = rgb.rgbGreen = rgb.rgbBlue = (byte)(hsl.Luminance * 255);
	}
	else
	{
		double v1, v2;
		double hue = (double)hsl.Hue / 360;

		v2 = (hsl.Luminance < 0.5) ?
			(hsl.Luminance * (1 + hsl.Saturation)) :
			((hsl.Luminance + hsl.Saturation) - (hsl.Luminance * hsl.Saturation));
		v1 = 2 * hsl.Luminance - v2;

		rgb.rgbRed = (byte)(255 * Hue_2_RGB(v1, v2, hue + (1.0 / 3)));
		rgb.rgbGreen = (byte)(255 * Hue_2_RGB(v1, v2, hue));
		rgb.rgbBlue	= (byte)(255 * Hue_2_RGB(v1, v2, hue - (1.0 / 3)));
	}
	return rgb;
}

double CColorConverter::Hue_2_RGB(double v1, double v2, double vH)
{
	if ( vH < 0 )
		vH += 1;
	if ( vH > 1 )
		vH -= 1;
	if ( ( 6 * vH ) < 1 )
		return ( v1 + ( v2 - v1 ) * 6 * vH );
	if ( ( 2 * vH ) < 1 )
		return v2;
	if ( ( 3 * vH ) < 2 )
		return ( v1 + ( v2 - v1 ) * ( ( 2.0 / 3 ) - vH ) * 6);
	return v1;
}

YCbCr CColorConverter::RGB2YCbCr(RGBQUAD rgb)
{
	double r = (double)rgb.rgbRed / 255;
	double g = (double)rgb.rgbGreen / 255;
	double b = (double)rgb.rgbBlue / 255;

	YCbCr ycbcr;
	ycbcr.Y = 0.2989 * r + 0.5866 * g + 0.1145 * b;
	ycbcr.Cb = -0.1687 * r - 0.3313 * g + 0.5000 * b;
	ycbcr.Cr = 0.5000 * r - 0.4184 * g - 0.0816 * b;
	return ycbcr;
}

RGBQUAD CColorConverter::YCbCr2RGB(YCbCr ycbcr)
{
	// don't warry about zeros. compiler will remove them
	double r = max(0.0, min(1.0, ycbcr.Y + 0.0000 * ycbcr.Cb + 1.4022 * ycbcr.Cr));
	double g = max(0.0, min(1.0, ycbcr.Y - 0.3456 * ycbcr.Cb - 0.7145 * ycbcr.Cr));
	double b = max(0.0, min(1.0, ycbcr.Y + 1.7710 * ycbcr.Cb + 0.0000 * ycbcr.Cr));

	RGBQUAD rgb;
	rgb.rgbRed = (byte)(r * 255);
	rgb.rgbGreen = (byte)(g * 255);
	rgb.rgbBlue	= (byte)(b * 255);
	return rgb;
}